﻿using System.Collections.Concurrent;
using System.Diagnostics;

namespace UPKAndUDKUnpackTools.Tools;

/// <summary>
///     处理 UPK 文件的类。
/// </summary>
public static class UpkProcessor
{
    private static readonly ParallelOptions Options;

    static UpkProcessor()
    {
        Options = new ParallelOptions { MaxDegreeOfParallelism = 3 };
    }

    public static event Action<int> OnProgress;

    /// <summary>
    ///     处理解锁的 UPK 文件。
    /// </summary>
    public static void ProcessFiles(bool isSounds)
    {
        var unpackedDir = Path.Combine(Directory.GetCurrentDirectory(), "unpacked");
        var filePaths = Directory.GetFiles(unpackedDir, "*.upk", SearchOption.AllDirectories);

        foreach (var filePath in filePaths) UnlockUpkFile(filePath, isSounds);

        OnProgress?.Invoke(100);
    }


    /// <summary>
    ///     解锁UPK文件
    /// </summary>
    /// <param name="filename">要解锁的UPK文件路径</param>
    /// <param name="isSounds">是否是声音文件</param>
    private static void UnlockUpkFile(string filename, bool isSounds)
    {
        // 创建解锁后的文件输出目录
        var outputDir = Path.Combine(Directory.GetCurrentDirectory(), "unlockedUpk");
        Directory.CreateDirectory(outputDir);
        // 构造解锁后文件的路径
        var newFilename = Path.Combine(outputDir, Path.GetFileNameWithoutExtension(filename) + "_unpacked.upk");
        // 检查文件是否已解锁，避免重复处理
        if (File.Exists(newFilename))
        {
            Console.WriteLine($"{GetSubstringFromRight(filename)} 文件已解锁，跳过处理。");
            return;
        }
        // 读取文件的所有字节
        var data = File.ReadAllBytes(filename);
        // 提取交换后的值
        var swappedValue = ExtractData(data);
        // 获取要删除的字节数
        var bytesToDelete = GetBytesToDelete(data, swappedValue, isSounds);
        // 检查计算的字节数是否超出文件长度
        if (swappedValue + bytesToDelete > data.Length)
        {
            Console.WriteLine("swappedValue + bytesToDelete 超出范围。");
            return;
        }
        // 打印文件处理信息
        Console.WriteLine($"文件：{GetSubstringFromRight(filename)} 偏移量为：{swappedValue:X8}，删除：{bytesToDelete} 个00字节。");
        // 创建一个新的字节数组，长度为原文件长度减去要删除的字节数
        var newData = new byte[data.Length - bytesToDelete];
        // 复制原文件数据到新数组，跳过要删除的部分
        Array.Copy(data, 0, newData, 0, swappedValue);
        Array.Copy(data, swappedValue + bytesToDelete, newData, swappedValue,
            data.Length - (swappedValue + bytesToDelete));
        // 构造最终的解锁文件路径
        var finalFilename = newFilename.Replace("_modified_unpacked.upk", ".upk");
        // 写入解锁后的文件
        File.WriteAllBytes(finalFilename, newData);
    }

    public static void ErrFileHandle(string filename)
    {
        var data = File.ReadAllBytes(filename);
        var swappedValue = ExtractData(data);
        if (swappedValue >= data.Length)
        {
            Console.WriteLine("swappedValue 超出范围。");
            return;
        }

        if (swappedValue + 4 > data.Length)
        {
            Console.WriteLine("swappedValue + 4 超出范围。");
            return;
        }

        Array.Copy(data, swappedValue + 4, data, swappedValue, data.Length - swappedValue - 4);
        Array.Resize(ref data, data.Length - 4);
        File.WriteAllBytes(filename, data);
        Console.WriteLine($"文件已修复: {filename}");
    }


    /// <summary>
    ///     计算给定数据数组中需要删除的字节数。
    /// </summary>
    /// <param name="data">包含数据的字节数组。</param>
    /// <param name="swappedValue">开始检查的起始位置。</param>
    /// <param name="isSounds">指示是否处理声音数据的布尔值。</param>
    /// <returns>需要删除的字节数量。</returns>
    private static int GetBytesToDelete(byte[] data, int swappedValue, bool isSounds)
    {
        // 初始化需要删除的字节数计数器
        var count = 0;
        // 定义要删除的字节数常量
        const int bytesToDelete = 27;

        // 遍历数组，从swappedValue+1位置开始，直到达到要删除的字节数或数组末尾
        for (var i = swappedValue + 1; i <= swappedValue + bytesToDelete && i < data.Length; i++)
        {
            // 增加计数器
            count++;
            // 根据当前字节值决定如何处理
            switch (data[i])
            {
                // 如果字节值为0x00，跳过当前循环继续检查下一个字节
                case 0x00:
                    continue;
                // 如果字节值为0xFF, 0xFC, 0x05, 0x0D, 0x01，根据isSounds和count的值决定返回值
                case 0xFF:
                case 0xFC:
                case 0x05:
                case 0x0D:
                case 0x01:
                    // 如果isSounds为true且count大于23，返回i - swappedValue - 4，否则返回i - swappedValue
                    return isSounds && count > 23 ? i - swappedValue - 4 : i - swappedValue;
                // 对于其他字节值，返回i - swappedValue - 4
                default:
                    return i - swappedValue - 4;
            }
        }

        // 如果遍历结束没有返回，返回bytesToDelete
        return bytesToDelete;
    }


    /// <summary>
    ///     从给定的字节数组中提取数据。
    /// </summary>
    /// <param name="data">要提取数据的字节数组。</param>
    /// <returns>提取到的数据；如果无法提取，则返回-1。</returns>
    private static int ExtractData(byte[] data)
    {
        if (data.Length < 0x29) return -1;
        var targetPosition = BitConverter.ToInt32(data, 0x25);
        var newPosition = targetPosition + 0x24;
        if (newPosition + 4 > data.Length) return -1;
        return BitConverter.ToInt32(data, newPosition);
    }


    /// <summary>
    ///     处理指定目录中的 UPK 文件。
    /// </summary>
    /// <param name="inputDir">输入目录。</param>
    /// <param name="outputDir">输出目录。</param>
    public static void ProcessUpkFiles(string inputDir, string outputDir)
    {
        Directory.CreateDirectory(outputDir);
        var filePaths = Directory.GetFiles(inputDir, "*.upk", SearchOption.AllDirectories);

        // 使用并行处理 ModifyUpkFile
        var modifiedFilePaths = new ConcurrentBag<string>();
        Parallel.ForEach(filePaths, Options, filePath =>
        {
            Console.WriteLine($"正在修改 {GetSubstringFromRight(filePath)}");
            var modifiedFilePath = ModifyUpkFile(filePath, outputDir);
            modifiedFilePaths.Add(modifiedFilePath);
            Console.WriteLine($"完成修改 {GetSubstringFromRight(filePath)}");
        });
        OnProgress?.Invoke(50);

        // 顺序执行 RunExternalPrograms
        foreach (var modifiedFilePath in modifiedFilePaths) RunExternalPrograms(modifiedFilePath);
    }


    /// <summary>
    ///     修改指定的 UPK 文件。
    /// </summary>
    /// <param name="filePath">文件路径。</param>
    /// <param name="outputDir">输出目录。</param>
    /// <returns>修改后的文件路径。</returns>
    private static string ModifyUpkFile(string filePath, string outputDir)
    {
        var data = File.ReadAllBytes(filePath);
        data[0] = 0xFF;
        data[1] = 0xA8;
        data[2] = 0x27;
        data[3] = 0x3E;
        data[4] = (byte)((data[4] + 8) % 256);
        var fileName = Path.GetFileName(filePath);
        var modifiedFilePath = Path.Combine(outputDir, fileName.Replace(".upk", "_modified.upk"));
        File.WriteAllBytes(modifiedFilePath, data);

        return modifiedFilePath;
    }

    /// <summary>
    ///     运行外部程序来处理修改后的文件。
    /// </summary>
    /// <param name="modifiedFilePath">修改后的文件路径。</param>
    private static void RunExternalPrograms(string modifiedFilePath)
    {
        Console.WriteLine($"使用 UnPack.exe 处理 {GetSubstringFromRight(modifiedFilePath)}");
        var startInfo = new ProcessStartInfo
        {
            FileName = "UnPack.exe",
            Arguments = modifiedFilePath,
            UseShellExecute = false,
            CreateNoWindow = true,
            RedirectStandardOutput = true
        };
        Process.Start(startInfo)?.WaitForExit();
        const string tempDir = "ucpUpk";
        Directory.CreateDirectory(tempDir);
        var currentDir = Directory.GetCurrentDirectory();
        foreach (var file in Directory.GetFiles(currentDir, "*_ucp.upk"))
        {
            var ucpFilePath = Path.Combine(currentDir, file);
            var destinationPath = Path.Combine(tempDir, Path.GetFileName(file));
            File.Move(ucpFilePath, destinationPath);
            var absoluteUcpPath = Path.GetFullPath(destinationPath);
            Console.WriteLine($"使用 Repair.exe 修复 {GetSubstringFromRight(modifiedFilePath)}");
            RunRepair(modifiedFilePath, absoluteUcpPath);
            using (var fs = new FileStream(modifiedFilePath, FileMode.Open, FileAccess.Write))
            {
                fs.Seek(0x04, SeekOrigin.Begin);
                fs.Write([0x2C, 0x03], 0, 2);
            }

            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine($"使用 decompress.exe 解压 {GetSubstringFromRight(modifiedFilePath)}");
            Console.ResetColor();
            var dec = new ProcessStartInfo
            {
                FileName = "decompress.exe",
                Arguments = modifiedFilePath,
                UseShellExecute = false,
                CreateNoWindow = true,
                RedirectStandardOutput = true
            };
            Process.Start(dec)?.WaitForExit();
        }
    }


    /// <summary>
    ///     运行 Repair.exe 修复文件。
    /// </summary>
    /// <param name="filePath">文件路径。</param>
    /// <param name="ucpPath">UCP 文件路径。</param>
    private static void RunRepair(string filePath, string ucpPath)
    {
        var startInfo = new ProcessStartInfo
        {
            FileName = "Repair.exe",
            Arguments = filePath, // 传递文件路径作为参数
            UseShellExecute = false,
            RedirectStandardInput = true,
            CreateNoWindow = true
        };

        using var process = Process.Start(startInfo);

        if (process != null)
        {
            using (var writer = process.StandardInput)
            {
                if (writer.BaseStream.CanWrite)
                {
                    writer.WriteLine("0");
                    writer.WriteLine(ucpPath);
                    writer.WriteLine("125");
                    writer.WriteLine();
                }
            }

            process.WaitForExit(60000);
        }
        else
        {
            Console.WriteLine("无法启动 Repair.exe。");
        }
    }

    private static string GetSubstringFromRight(string input)
    {
        if (string.IsNullOrEmpty(input)) return string.Empty;
        // 找到最后一个 '/' 或 '\' 的位置
        var lastIndex = input.LastIndexOfAny(['/', '\\']);

        // 如果没有找到，返回整个字符串
        return lastIndex == -1
            ? input
            :
            // 从最后一个 '/' 或 '\' 之后开始取子字符串
            input[(lastIndex + 1)..];
    }
}